/**
 * CS371P C++ Project Utilities
 * Implementation file: ProjectUtil.c++
 *
 * @author Jonathan Bernard (jdbernard@gmail.com)
 *
 * This work is copyright 2009 by Jonathan Bernard. All rights reserved.
 * This code is NOT open source (at this time). Redistribution rights are
 * explicitly reserved by Jonathan Bernard. Jonathan Bernard has granted
 * said rights to Lincoln Simmons for the purposes of UT CS371P Project 1
 *
 * @brief This library contains a simple command line parser. Since I wrote
 * this rather quickly, it is not yet documented. It should be by the end of
 * project 2.
 */

#include "ProjectUtil.h"

// ---------------------
// Option Implementation
// ---------------------

Option::Option(const char* shortName) {
    _shortName = new char[strlen(shortName) + 1];
    strcpy(_shortName, shortName);

    _longName = NULL;
    _description = NULL;
    _values = NULL;
    _required = false;
    _numArgs = 0;
}

Option::~Option() {
    if (_shortName != NULL) {
        delete [] _shortName;
        _shortName = NULL;
    }

    if (_longName != NULL) {
        delete [] _longName;
        _longName = NULL;
    }

    if (_description != NULL) {
        delete [] _description;
        _description = NULL;
    }

    _invalidateValues();
}

Option &Option::longName(const char* name) {
    if (_longName != NULL) {
        delete [] _longName;
    }

    _longName = new char[strlen(name) + 1];
    strcpy(_longName, name);

    return *this;
}

Option &Option::numArgs(int numArgs) {
    _numArgs = numArgs;

    return *this;
}

Option &Option::description(const char* description) {
    if (_description != NULL) {
        delete [] _description;
    }

    _description = new char[strlen(description) + 1];
    strcpy(_description, description);

    return *this;
}

Option &Option::required(bool required) {
    _required = required;

    return *this;
}

bool Option::isSet() {
    return _values != NULL;
}

bool Option::isRequired() {
    return _required;
}

void Option::getShortName(char* out, int maxlen) {
    if (maxlen > 0) {
        strncpy(out, _shortName, maxlen);
    } else {
        strcpy(out, _shortName);
    }
}

const char *Option::getShortName() {
    return (const char*) _shortName;
}

void Option::getLongName(char* out, int maxlen) {
    if (maxlen > 0) {
        strncpy(out, _longName, maxlen);
    } else {
        strcpy(out, _longName);
    }
}

const char *Option::getLongName() {
    return (const char *) _longName;
}

/* TODO: void Option::getDescription(char *out, int maxlen) {

}*/

const char *Option::getDescription() {
    return (const char *) _description;
}

int Option::getNumArgs() {
    return _numArgs;
}

/*void Option::setValues(int maxlen, ...) {
    va_list varArgs;
    va_start(varArgs, _numArgs);

    // if the values haven't been set
    if (_values == NULL) {
        // init array
        _values = new char*[_numArgs];
        // init each pointer
        for (int i = 0; i < _numArgs; i++) {
            _values[i] = NULL;
        }
    }

    for (int i = 0; i < _numArgs; i++) {
        const char* inValue = va_arg(varArgs, const char*);

        // if this value is already set
        if (_values[i] != NULL) {
            // delete it
            delete _values[i];
            _values[i] = NULL;
        }

        if (maxlen > 0) {
            _values[i] = new char[maxlen];
            strncpy(_values[i], inValue, maxlen);
        } else {
            _values[i] = new char[strlen(inValue)];
            strcpy(_values[i], inValue);
        }
    }

    va_end(varArgs);
}*/

void Option::setValues(char** inValues, int maxlen) {

    if (_values == NULL) {
        // init array
        _values = new char*[_numArgs];
        // init each pointer
        for (int i = 0; i < _numArgs; i++) {
            _values[i] = NULL;
        }
    }

    for (int i = 0; i < _numArgs; i++) {
        if (_values[i] != NULL) {
            // delete it
            delete [] _values[i];
            _values[i] = NULL;
        }

        if (maxlen > 0) {
            _values[i] = new char[maxlen];
            strncpy(_values[i], inValues[i], maxlen);
        } else {
            _values[i] = new char[strlen(inValues[i])];
            strcpy(_values[i], inValues[i]);
        }
    }
}

void Option::getValues(char** out, int maxlen) {
    if (!isSet()) {
        for (int i = 0; i < _numArgs; i++) {
            strcpy(out[i], "");
        }
        return;
    }

    for (int i = 0; i < _numArgs; i++) {
        if (maxlen > 0) {
            strncpy(out[i], _values[i], maxlen);
        } else {
            strcpy(out[i], _values[i]);
        }
    }
}

const char *Option::getValue() {
    if (_numArgs < 1 || !isSet()) {
        return "";
    }

    return (const char *) _values[0];
}

void Option::printShort(ostream& out) {
    if (!_required) {
        out << "[";
    }

    out << _shortName;

    for (int i = 0; i < _numArgs;) {
        out << " <arg" << ++i << ">";
    }

    if (!_required) {
        out << "]";
    }
}

void Option::print(ostream& out) {

    out << _shortName;

    if (_longName != NULL) {
        out << ", " << _longName;
    }

    for (int i = 0; i < _numArgs;) {
        out << " <arg" << ++i << ">";
    }

    out << endl;

    if (_required) {
        out << "\tRequired. ";
    }

    if (_description != NULL) {
        if (!_required) {
            out << "\t";
        }
        out << _description;
    }

    if (_required || (_description != NULL)) {
        out << endl;
    }
}

void Option::_invalidateValues() {
    if (_values != NULL) {
        /*for (int i = 0; i < _numArgs; i++) {
            if (_values[i] != NULL) {
                delete [] _values[i];
                _values[i] = NULL;
            }
        }*/
        delete [] _values;
        _values = NULL;
    }
}

// ------------------------
// SimpleCli Implementation
// ------------------------

SimpleCli::SimpleCli(const char* progName) {
    _options = new Option*[10];
    _capacity = 10;
    _numOptions = 0;

    _args = NULL;
    _numArgs = 0;

    _progName = new char[256];
    strncpy(_progName, progName, 256);

    _set = false;
}

SimpleCli::~SimpleCli() {
    for (int i = 0; i < _numOptions; i++) {
        delete _options[i];
        _options[i] = NULL;
    }

    delete [] _options;

    for (int i = 0; i < _numArgs; i++) {
        delete [] _args[i];
        _args[i] = NULL;
    }

    delete [] _args;
    _args = NULL;

    delete [] _progName;
    _progName = NULL;
}

Option &SimpleCli::makeOption(const char* shortName) {
    if (_numOptions == _capacity) _resize();

    _options[_numOptions++] = new Option(shortName);

    _invalidateArgs();

    return *_options[_numOptions - 1];
}

Option *SimpleCli::getOption(const char* shortName) {
    int nameLen = strlen(shortName);
    char *optName = new char[nameLen];
    for (int i = 0; i < _numOptions; i++) {
        _options[i]->getShortName(optName, nameLen);
        if (strncmp(shortName, optName, nameLen) == 0) {
            delete [] optName;
            return _options[i];
        }
    }

    delete [] optName;
    return NULL;
}

int SimpleCli::getNumberOfOptions() {
    return _numOptions;
}

void SimpleCli::getOptions(Option** optionsOut) {
    for (int i = 0; i < _numOptions; i++) {
        optionsOut[i] = _options[i];
    }
}

void SimpleCli::parse(int argc, char* argv[]) {
    char sn[256];   // current option shortname
    char ln[256];   // current option longname
    bool isOption;
    int *argsIndices = new int[argc];
    argsIndices[0] = 0;
    _numArgs = 0;

    _invalidateArgs();

    // iterate over args
    for (int i = 1; i < argc; i++) {
        isOption = false;
        argsIndices[i] = 0;

        // iterate over options
        for (int j = 0; j < _numOptions; j++) {

            _options[j]->getShortName(sn, 256);
            _options[j]->getLongName(ln, 256);
            // if this matches set the option
            if ((strncmp(ln, argv[i], 256) == 0) ||
                (strncmp(sn, argv[i], 256) == 0)) {

                isOption = true;

                // read the arguments and store in the option
                char** valArr = new char*[_options[j]->getNumArgs()];
                for (int k = 0; k < _options[j]->getNumArgs(); k++) {
                    if ((i+k+1) >= argc) {
                        stringstream msg;
                        msg << "Missing required argument for option '";
                        msg << sn << "' (expected ";
                        msg << _options[j]->getNumArgs();
                        msg << " arguments)." << endl;
                        throw invalid_argument(msg.str());
                    } else {
                        valArr[k] = new char[strlen(argv[i+k+1])];
                        strcpy(valArr[k], argv[i+k+1]);
                    }
                }
                _options[j]->setValues(valArr, 256);
                delete [] valArr;
                break;
            }
        }

        if (!isOption) {
            argsIndices[i] = i;
        }
    }

    // collect standalone arguments with no option
    _args = new char*[_numArgs];
    int j = 0;
    for (int i = 0; i < argc; i++) {
        if (argsIndices[i] != 0) {
            _args[j] = new char[strlen(argv[argsIndices[i]])];
            strcpy(_args[j], argv[argsIndices[i]]);
            j++;
        }
    }

    // check that all required options are supplied
    for (int i = 0; i < _numOptions; i++) {
        Option &opt = *_options[j];
        if (opt.isRequired() && !opt.isSet()) {
            stringstream msg;
            msg << "Required option is missing: '";
            msg << opt.getShortName() << "'" << endl;
            throw invalid_argument(msg.str());
        }
    }

    _set = true;

    delete [] argsIndices;
}

bool SimpleCli::isSet() {
    return _set;
}

bool SimpleCli::isSet(const char* shortName) {
    Option* opt = getOption(shortName);
    if (opt == NULL) {
        return false;
    }

    return opt->isSet();
}

const char *SimpleCli::getValue(const char *shortName) {

    if (!isSet() || !isSet(shortName)) {
        return "";
    }

    return getOption(shortName)->getValue();
}

int SimpleCli::getNumberOfArgs() {
    return _numArgs;
}

void SimpleCli::getArgs(char** out, int maxlen) {
    if (!isSet()){
        for (int i = 0; i < _numArgs; i++) {
            strcpy(out[i], "");
        }
        return;
    }

    for (int i = 0; i < _numArgs; i++) {
        if (maxlen > 0) {
            strncpy(out[i], _args[i], maxlen);
        } else {
            strcpy(out[i], _args[i]);
        }
    }
}

void SimpleCli::printUsage(ostream& out) {
    out << "Usage: " << _progName;
    for (int i = 0; i < _numOptions; i++) {
        out << " ";
        _options[i]->printShort(out);
    }

    out << endl;

    for (int i = 0; i < _numOptions; i++) {
        _options[i]->print(out);
    }

    out << endl;
}

void SimpleCli::_resize() {
    int newSize = _numOptions * 2 + 1;
    Option** oldOpts = _options;
    _options = new Option*[newSize];

    for (int i = 0; i < _numOptions; i++) {
        _options[i] = oldOpts[i];
    }

    delete [] oldOpts;
}

void SimpleCli::_invalidateArgs() {
    if (_args != NULL) {
        /*for (int i = 0; i < _numArgs; i++) {
            if (_args[i] != NULL) {
                delete [] _args[i];
                _args[i] = NULL;
            }
        }*/
        delete [] _args;
        _args = NULL;
    }
    _numArgs = 0;

    _set = false;
}
